Java EE 7 Database-based Apache Shiro configuration

Source: Internet
Author: User
Tags base64 base64 encode jboss

In the previous article I introduced the basic method of configuring Shiro in the Java EE environment, but in the real development process we basically do not
will use profile-based user role configuration, in most cases we will store users, roles and permissions in the database, and then we tell Shiro to go to the database to fetch data, so the configuration is more flexible and more powerful.

So that the Shiro can read the database (or LDAP, file system, etc.) of the components called realm, you can think of realm as a security-specific DAO, let me explain in detail how to configure realm:

(The technology used is similar to the one in the previous article, in addition, we used JPA, project source reference my GitHub)

Add dependency

On the basis of the dependencies used in the previous article, we also need to add:

<dependency>  <groupId>Org.hibernate.javax.persistence</groupId>  <artifactId>Hibernate-jpa-2.1-api</artifactId>  <version>1.0.0.Final</version></dependency><dependency>  <groupId>Org.jboss.spec.javax.ejb</groupId>  <artifactId>Jboss-ejb-api_3.2_spec</artifactId>  <version>1.0.0.Final</version>  <scope>Provided</scope></dependency><dependency>  <groupId>Org.apache.commons</groupId>  <artifactId>Commons-lang3</artifactId>  <version>3.7</version></dependency>
Add entities such as user ER diagram

Here I define the relationship of the user, role, and Permissions (Permission) as:

For simplicity, the ER diagram omits the table's fields, which only describe the relationships between the tables: one user can have multiple roles, and one role has multiple permissions.

JPA Entities

src/main/java/com/github/holyloop/entity/User.java:

 @Id  @ Generatedvalue  (strategy = Generationtype.)  @Column  (unique = true , nullable =  False ) private  Long ID;  @Column  (nullable = false , unique = true ) private  String username;  @Column  (nullable = false ) private  String password;  @Column  (nullable = false ) private  String Salt;  @OneToMany  (Mappedby =  "user" ) private  set<userrolerel> userrolerels = new  hashset<> (0 );  

Here for the password ciphertext password, salt for the hash encryption used in the salt, we let each user has a random salt.

src/main/java/com/github/holyloop/entity/Role.java:

 @Id   @GeneratedValue  (strategy = GenerationType. identity )  @Column  (unique = true , nullable = false ) private  Long ID;  @Column  (name =  "Role_name" , nullable = false , unique = true< /span>) private  String roleName;  @OneToMany  (Mappedby =  "role" ) private  set<userrolerel> userrolerels = new  hashset<> (0 );  @OneToMany  (Mappedby =  "role" ) private  set< rolepermissionrel> rolepermissionrels = new  hashset<> (0 ); 

roleNameAs the name describes, the permission name.

src/main/java/com/github/holyloop/entity/Permission.java:

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Columntruefalse)private Long id;@Column"permission_str"false)private String permissionStr;

permissionStrAs a permission string, I generally prefer to define a permission string as a resource-type:operation:instance permission that means an operation (operation) resource (resource-type) instance (instance), for example: org:update:1 is the "Update organization with ID 1" permission. This kind of organization authority is more flexible, can be accurate to the resource instance , can use this way in the system that the permission request teaches high, generally to the system that the permission does not require accurate to the resource instance uses the traditional RBAC to satisfy the request. A detailed definition of permissions can be found in the Shiro documentation.

The remaining two intermediate relational tables are simpler and are not explained in detail here.

Modify Shiro.ini

src/main/webapp/WEB-INF/shiro.ini:

[main]Authc.loginurl=/loginCredentialsmatcher=Org.apache.shiro.authc.credential.Sha256CredentialsMatchercredentialsmatcher.storedcredentialshexencoded= falsecredentialsmatcher.hashiterations= 1024x768Dbrealm=Com.github.holyloop.secure.shiro.realm.DBRealmDbrealm.credentialsmatcher=$credentialsMatcherSecuritymanager.realms=$dbRealm[URLs]/index.html=Anon/login=authc

Compared to the previous configuration, I deleted the [users] and [roles] block, because this data we will load from the database. In addition, I configured the Shiro password match credentialsMatcher , it will use the sha256 to do the password match; and then there's the focus of this article dbRealm , this real M will be implemented by ourselves.

Achieve Realm

We DBRealm will inherit Authorizingrealm and then overwrite its doGetAuthorizationInfo and doGetAuthenticationInfo methods, these two methods are used to obtain permission information and authentication information:

src/main/java/com/github/holyloop/secure/shiro/realm/DBRealm.java:

@OverrideprotectedAuthorizationinfoDogetauthorizationinfo(PrincipalCollection principals) {String username = stringutils.Trim(String) Principals.Getprimaryprincipal()); set<string> roles = UserService.Listrolesbyusername(username); set<string> permissions = UserService.Listpermissionsbyusername(username); Simpleauthorizationinfo Authorizationinfo =New Simpleauthorizationinfo(); Authorizationinfo.Setroles(roles); Authorizationinfo.setstringpermissions(permissions);returnAuthorizationinfo;}@OverrideprotectedAuthenticationInfoDogetauthenticationinfo(Authenticationtoken token)throwsauthenticationexception {String username = stringutils.Trim(String) token.Getprincipal());if(StringUtils.IsEmpty(username)) {Throw NewAuthenticationexception (); } User user = UserService.Getonebyusername(username);if(User = =NULL) {Throw NewAuthenticationexception (); } Simpleauthenticationinfo AuthenticationInfo =New Simpleauthenticationinfo(username, user.)GetPassword(),New Simplebytesource(Base64.Decode(User.GetSalt())),GetName());returnAuthenticationInfo;}

doGetAuthorizationInfoThe user's role and permission data are loaded, and the user's doGetAuthenticationInfo authentication data (cipher + salt) is found based on the password entered by the user.

In addition, both methods are used UserService , and its implementation is relatively simple, such as getOneByUsername :

publicgetOneByUsername(String username) {  if (StringUtils.isEmpty(username)) {    thrownew IllegalArgumentException("username must not be null");  }  try {    return userRepository.getOneByUsername(username);  catch (NoResultException e) {    returnnull;  }}

UserServiceis dependent UserRepository , it is a JPA-based database access interface, specific implementation details can refer to my GitHub source code.

Add user

Up to this point, the user certification and authentication section has been basically completed. But only the authentication function is incomplete for a system, and we also need to be able to add new users. Let's UserService add an interface to add a new user:

 Public void AddUser(userdto user)throwsusernameexistedexception {if(User = =NULL) {Throw NewIllegalArgumentException ("User must not being null"); }Try{User Duplicateuser = userrepository.Getonebyusername(User.GetUserName());if(Duplicateuser! =NULL) {Throw New usernameexistedexception(); }  }Catch(Noresultexception e) {} twotuple<string, string> hashandsalt = Credentialencrypter.Saltedhash(User.GetPassword()); User entity =New User(); Entity.Setusername(User.GetUserName()); Entity.SetPassword(Hashandsalt.T1); Entity.Setsalt(Hashandsalt.T2); Userrepository.Save(entity);}

UsernameExistedExceptionAs the exception name describes, the exception is thrown when the user name entered by the new user is repeated with the existing user. The UserDTO structure is as follows:

src/main/java/com/github/holyloop/dto/UserDTO.java:

private String username;private String password;

Here I define it very simple, the actual project development when it will certainly have more properties, such as mailbox, birthday, mobile phone number and so on.

Another important feature is password encryption, where I implemented CredentialEncrypter this cipher:

src/main/java/com/github/holyloop/secure/shiro/util/CredentialEncrypter.java:

Private StaticRandomNumberGenerator rng =New Securerandomnumbergenerator();/*** hash + salt* * @param plaintextpassword* PlainText Password* @return*/ Public StaticTwotuple<string, string>Saltedhash(String Plaintextpassword) {if(StringUtils.IsEmpty(Plaintextpassword)) {Throw NewIllegalArgumentException ("Plaintextpassword must not being null"); } Object salt = rng.nextbytes(); String hash =New Sha256hash(Plaintextpassword, salt,1024x768).toBase64();return NewTwotuple<> (hash, salt.toString());}

For higher security, we generate a random salt for each user RandomNumberGenerator , saltedHash encrypt the plaintext password and the random salt Sha256 and then Base64 encode. The method returns the ciphertext password and the random salt.

Here, the entire system of encryption and decryption function is basically completed, then we have a simple application.

Basic app Add Rest interface

Here we implement an interface to add a new user:

src/main/java/com/github/holyloop/rest/controller/UserController.java:

@RequiresPermissions("User:insert")@POST@Consumes(mediatype.Application_json)@Produces(mediatype.Application_json) PublicResponseAddUser(userdto user) {if(User = =NULL|| StringUtils.IsEmpty(User.GetUserName()) || StringUtils.IsEmpty(User.GetPassword())) {returnResponse.Status(Status.bad_request).Entity("username or password must not is null").Build(); } user.Setusername(StringUtils.Trim(User.GetUserName())); User.SetPassword(StringUtils.Trim(User.GetPassword()));Try{UserService.AddUser(user); }Catch(Usernameexistedexception e) {returnResponse.Status(Status.bad_request).Entity("username existed").Build(); }returnResponse.Status(Status.OK).Build();}

I have implemented this interface in a jax-rs style, and the logic is simple: verifying that the user input is legitimate, calling to add the user if it is valid, UserService.addUser() and returning 200 when the add succeeds; it is important to note that I add an annotation to the interface that @RequiresPermissions("user:insert") indicates that the caller of the interface needs to have .

Deployment test

Next we'll apply the deployment and use curl for a few simple tests.

    • To test the defined @RequiresPermissions("user:insert") permission requirements:
curl -i -d "username=guest&password=guest" -c cookies "localhost:8080/shiro-db/login"curl -i -X POST -H "content-type:application/json" -b cookies > -d ‘{"username":"newuser", "password":"123"}‘ > localhost:8080/shiro-db/api/user

Here I log in as Guest user, the user does not have "User:insert" permission, can see the request to add the user interface response to 403,:

Then change to a user who has the "User:insert" permission to log on:

curl -i -d "username=root&password=secret" -c cookies "localhost:8080/shiro-db/login"curl -i -X POST -H "content-type:application/json" -b cookies > -d ‘{"username":"newuser", "password":"123"}‘ > localhost:8080/shiro-db/api/user

You can see that the response is 200, and then we go to the database and look at the newly added "NewUser" User:

You can see that the user was added successfully, and the password is ciphertext stored

    • Test new User Login
curl -i -d "username=newuser&password=123" -c cookies "localhost:8080/shiro-db/login"

Login successful.

    • Add the new user repeatedly

We define in the logic of adding users that if the user name repeats, the new user should add the failure:

As we expected, the server response was 400 and prompted us to "username existed".

The above is Shiro database-based users, roles, permissions configuration method, the core is the realm of the configuration, realm is responsible for obtaining the user's authentication and authorization information; In order to improve the storage security of authentication information, we also encrypt the password (which is necessary for a complete system). For a slightly larger system, pure database-based certification/authentication mechanism is not enough, in order to improve efficiency also need to add some caching mechanism, this article for the time being here, about the cache, we talk about later.

Java EE 7 Database-based Apache Shiro configuration

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.