Archived

northern trust company trustee fee schedule, chief operating officer alternative titles, compare and contrast social science natural science and humanities, uber child seat policy qld, black gospel concerts 2022, male quaker parrot mating behavior, main value of humanities in defining ethics, kevin sizemore family, rever d’entendre son prenom islam, husman hall xavier university, gombo et estomac, robert thirsk high school ranking, my john lewis gift card has expired what can i do, how to remove a hashtag on tiktok, walter eric lumsden,Related: anthem blue cross blue shield plan code 131, d2 players in cfl, st albans messenger police log, faulkner painting columbus, converted garage for rent in san jose, ca, edward armstrong obituary, jody johnston totie fields daughter, dave brown memphis daughter, does eddie bauer run big or small, accommodation in bristol for students, who is the male dancer in the warrior video, grand mollusque bivalve 5 lettres, michael symon olivia wilson, wsop geolocation plugin, hamtaro official website,Related: jordan davidson pinsky, michael wallace “mikey” hawley, do bones decompose in water, salvatore scallopini creamy romano salad dressing, kim richards ken blumenfeld baby, swag mode premium cracked, christine talbot first husband, odessa, texas crime rate, michael walker obituary parkersburg wv, dearborn special olympics, stephanie paisley biography, a vendetta mark scheme, gonzaga high school basketball recruits, chase kalisz family tree, marc maron sarah cain split,Related: what happened to melissa cerniglia, alisa kotlyarenko net worth, townhouses for sale in the lakes blaine, mn, ryan fitzgerald yarn, cpt code for medial cuneiform osteotomy, bangkok avenue broomfield, how far is the ark encounter from nashville, french air traffic control strike today, university of north carolina dental school tuition, the aleph in the mirror, a point inside the production possibilities curve is, farmwood turkey breast roast 1kg, bronx zoo snowball cause of death, united brotherhood of carpenters pension fund, pro golfers living in the woodlands tx,Related: aqua chalet houseboat company, why were southerners unable to maintain unity in the people’s party quizlet, carbon nation tribe, disney princess makeover games, metroid prime hunters sound effects, falar sobre mim na entrevista, engagement of the feet in dance, heinz marecek krebserkrankung, john heilemann laptop logo, list of assistant commissioner of police in nigeria, annemarie brenning cause of death, robinhood unable to login with provided credentials, major highways in the west region usa, best items at bj’s restaurant, washington county maryland code enforcement,Related: perruche catherine odeur, traumatólogo especialista en hombro arequipa, danaher business system ppt, tipos de gestão de projetos, ang kwento ni mabuti moral lesson, rob halford partner thomas, ridges in cheeks after facelift, graspp se cippoe, legend car setup tools, nicole schoen billy squier, bloxburg script pastebin 2021 october, list nurses struck off nmc register, field survey party members and functions, auking projector software update, wedding venues iron range mn,Related: garrett mcnamara first family, commbank current balance unavailable, western fence lizard lifespan, steve bartelstein wife, mesquite tree growing zones, fabricio itte robert whittaker split, right to know arkansas teacher salaries, victor french funeral, why did richard goulding leave the windsors, semaphore reef gps marks, ny certificate of auto repair, bethel university volleyball roster, alte gedichte aus der schulzeit, ouvir playlist gratis, georgetown 7 series gt7 32j7,Related: youngest taekwondo grandmaster, texto expositivo sobre los derechos, holy family fresh meadows bulletin, buy here pay here semi trucks, weeks until september 1 2023, pit people best combinations, what happened to alan on gem shopping network, timber faller jobs near me, detroit radio personalities, headley funeral home augusta ks, all i want for christmas market niagara falls, pryzm tickets birmingham, bala lake boat hire, singapore driver job for work permit, harrison line crew lists,Related: wisdom sits in places summary, monkey mouth dentures, nina hansen sig’s daughter, diana cooper debakey, what does the bennington flag mean today, version contemporaine de l’autoportrait, marques johnson family, robert weisberg ambassador, jesup, ga newspaper obituaries, sanaur police station contact number, what makes finfish vulnerable due to ocean acidification, can an employer refuse to verify employment, melhor smartphone 2022 portugal, huddersfield royal infirmary ward 9, albert lea police department officers,Related: docker swarm monitoring, bernedoodle for sale kansas city, toy poodles for sale in orange beach alabama, rottweiler pulling harness, samoyed husky puppies for sale near me, docker build ssh agent forwarding, best exercise for jack russell terrier, basenji breeders nova scotia, 4000 beagles virginia adoption, havanese breeders tucson, az, homemade dog food for great danes, cockapoo for sale by owner near jeddah, saint bernard poodle mix for sale, bracco italiano puppies for sale california, mini bernedoodle puppies f1b,Related: maltese puppies for sale in edmond, ok, maltipoo puppies craigslist near rome, metropolitan city of rome, hoobly belgian malinois florida, docker override entrypoint, miniature poodles midwest, kafka broker docker compose, mini sheepadoodle idaho, labradoodle puppies for sale maryland, why is chihuahua, mexico so dangerous, docker: error response from daemon: no command specified, cream french bulldog for sale near me, red labradoodle full grown, saint bernard puppies in south carolina, retired goldendoodles for sale ohio, boston terrier puppies copley, ohio,Related: cocker spaniel breeder pennsylvania, papillon puppies for adoption in texas, clumber spaniel kennel, teacup applehead chihuahua for sale near me, golden retriever back drawing, docker compose ebs volume, newfoundland x golden retriever, golden retriever slender head, scottish terrier summer cut, beaglebone black gpio internal pull up, pointer in data structure geeksforgeeks, australian labradoodle breeders maryland, island guard cane corso, chow mastiff for sale near netherlands, rottweiler teeth problems,Related: docker serial port linux, heaven sent dachshund rescue near chon buri, boston terrier for sale johannesburg, bull terrier puppies austin, tx, lab collie mix puppy for sale near alabama, , how to get golden retriever puppy, retired standard poodle for adoption illinois, brussels griffon breeders australia, colima docker credential desktop, brindle bullmastiff cropped ears, jack russell terrier for sale near new york, ny, collierville pet hospital grooming, labradoodle puppies adelaide, ardenada brussels griffon,Related: airedale terrier for sale nc, bulldog and chihuahua cartoon, boston terrier breeders south africa, french bulldog multiple seizures, large akita puppies for sale near berlin, bullmastiff care tips, german shorthaired pointer breeder michigan, black puppy pomeranian, havanese puppies for sale calgary, great dane poodle mix puppies for sale near maryland, flat coated retriever weight, shiba inu breeders east coast, golden retrievers for sale in platteville, wi, rottweiler breeders in san diego, standard american bulldog,

Archived

most expensive tequila, does hardee’s serve burgers all day, matthew kelly wife meggie, happn read receipts, bullsnake range map, hillary clinton height, fei long supermarket weekly ad, z line irregular 40 cm from the incisors, drogas la rebaja virtual, big ten vs sec bowl record last 10 years, blague pour rire en famille, a quel endroit devons nous retrouver james joyce triskelion, highway 20 oregon accident today, siobhan mckay thomas craig, epam glassdoor salaries,

Archived

keith nester wife, how far from a fire hydrant can you park, perodua manufacturing sdn bhd contact number, bodelwyddan castle hotel menu, gemini astronauts still alive, prince george radio death, belted galloway vs scottish highland, 2021 22 san antonio spurs, nick dougherty, bobby burkett football player, 1 million colombian pesos to dollars in 1990, heather small married, the nothing man ending explained, universal credit underpayment forum, pat devin covenant of the goddess,

Archived

redbud roots buchanan, mi address, metlife stadium section 113, large wading bird now only found in cambodia, when do ben and adrian sleep together, i really appreciate your support in this matter, similarities of 10 commandments in exodus and deuteronomy, why was walter baldwin replaced on the andy griffith show, how old is alice roberts husband, how to change battery in omron digital thermometer, opposite of “final nail in the coffin”, dominant signs in natal chart calculator, kat graham this is us, weird laws in luxembourg, how long will a goose sit on dead eggs, wagyu cattle company slippery rock pa,

Testing databases with Testcontainers and Liquibase

Testcontainers make it easy to test your database integration by providing docker containers for many databases, which can be started from within your unittest. This is great, but it requires a bit of work to get started since the databases are empty. Liquibase is a tool to manage database schema changes. Combining the two is an easy way to get the right schema to use in your tests. For this post we’re going to use both frameworks to setup and test a MySQL database.

Let’s start with creating a test which uses testcontainers. First we need to add some maven dependencies.

    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>testcontainers</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>mysql</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
    </dependency>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>junit-jupiter</artifactId>
      <scope>test</scope>
    </dependency>

We’re also going to add a dependencyManagement section:

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers-bom</artifactId>
        <version>1.16.3</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

That should contain all the dependencies we need. Docker also needs to be installed in order for the container to start.

Now, let’s create a test that uses a MySQL container.

@Testcontainers
public class TestcontainersTest {

    @Container
    public MySQLContainer<?> mySQLContainer = new MySQLContainer("mysql:8.0.28");

    @Test
    public void testContainer(){
        mySQLContainer.start();
        try(Connection conn = mySQLContainer.createConnection("")){
            Statement statement = conn.createStatement();
            statement.execute(mySQLContainer.getTestQueryString());
            ResultSet resultSet = statement.getResultSet();
            resultSet.next();
            int result = resultSet.getInt(1);
            System.out.println(result);
        } catch (SQLException e){
            fail(e.getMessage());
        }
    }
}

The first thing to notice is the @Testcontainers annotation. This will tell the Testcontainers framework to look for containers to start. Which brings us to the next thing, the @Container annotation. This will create a container. In this case the container will be a MySQL container, specifically running version 8.0.28.

Once the container has been created, it is not started yet. In the first line of our test, we start it by calling the .start() method. After that, we request a JDBC connection to the container, and we’re set.

But we don’t have a useful database yet. That’s where Liquibase comes in. We need to add the Liquibase dependency, create the scripts that update the database, and tell Liquibase to run those scripts.

    <dependency>
      <groupId>org.liquibase</groupId>
      <artifactId>liquibase-core</artifactId>
      <version>4.7.1</version>
    </dependency>

We’ll save the liquibase scripts in /src/main/resources/database, so we can access them from the unittest.

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
    <include file="change_001.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
    <changeSet id="001" author="ghyze.nl">
        <createTable tableName="PERSON">
            <column name="id" type="INT">
                <constraints nullable="false" primaryKey="true" primaryKeyName="pk_person" />
            </column>
            <column name = "firstname" type="varchar(255)" />
            <column name = "lastname" type="varchar(255)" />
        </createTable>
    </changeSet>
</databaseChangeLog>

And then we add the code to execute the scripts to the unittest:

    @Test
    public void testContainer(){
        mySQLContainer.start();
        try(Connection conn = mySQLContainer.createConnection("")){
            Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(conn));
            Liquibase liquibase = new Liquibase("/database/changelog.xml", new ClassLoaderResourceAccessor(), database);
            liquibase.update("test");

			// some more code
        } catch (SQLException | LiquibaseException e){
            fail(e.getMessage());
        }
    }

The liquibase.update() statement needs a context. This is a way to execute or leave out some scripts. For our test this doesn’t really matter, so we just put in “test”.

And now to proof that it works, let’s insert a record in the database and read it again:

    @Test
    public void testContainer(){
        mySQLContainer.start();
        try(Connection conn = mySQLContainer.createConnection("")){
            Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(conn));
            Liquibase liquibase = new Liquibase("/database/changelog.xml", new ClassLoaderResourceAccessor(), database);
            liquibase.update("test");

            PreparedStatement preparedStatement = conn.prepareStatement("insert into PERSON values(?, ?, ?)");
            preparedStatement.setInt(1, 1);
            preparedStatement.setString(2, "john");
            preparedStatement.setString(3, "doe");
            preparedStatement.execute();

            Statement statement = conn.createStatement();
            statement.execute("select * from PERSON");
            ResultSet resultSet = statement.getResultSet();
            resultSet.next();
            int id = resultSet.getInt(1);
            String firstName = resultSet.getString(2);
            String lastName = resultSet.getString(3);
            System.out.println("id: "+id+", first name:"+firstName+", last name:"+lastName);
        } catch (SQLException | LiquibaseException e){
            fail(e.getMessage());
        }
    }

And the result should look something like this:
Testcontainers test result

Conclusion

When your system uses a database, that database should be tested. There are different ways to setup and test the database, and Testcontainers is one of them. The advantage of using testcontainers is that the test is close to how the software will run on production. The disadvantage is that these tests are slow, and should be treated more as integration tests than regular unittests. In other words, you don’t want to run these tests all the time. Also, Docker needs to be installed and running. But just starting a MySQL container isn’t enough. We can’t test an empty database. Liquibase can be used to setup the database to get it in a testable state.

Verify logback log statements

The basic idea behind unittests is that they prove that the code does what it’s expected to do. That means you have to prove that the code is executed in the first place. But sometimes the only way to prove that the code is executed, is to verify that something is logged. With the recent Log4Shell exploit in mind, we’re going to use a different framework: logback.

Initial setup

Because we want to use this technique in multiple tests, we’re going to create a separate class. This class should capture all logging events for a specific class under test, and store them for later retrieval. So let’s start

public class TestUtils {

  private final Object objectUnderTest;
  
  private final List<ILoggingEvent> loggingEvents = new ArrayList<>();
  
  public TestUtils(Object objectUnderTest){
    this.objectUnderTest = objectUnderTest;
  }
}

Notice that we set this up with an actual object instead of a class. We could just use the class instead. I have chosen to include the actual object under test, so the TestUtils class can include some reflection helpers. But that is beyond the scope of this post.

Setup logging

To start capturing the logging statements, we need to create an appender. Once the appender is created, we need to start it and configure the logger to use it. The appender has one important method: append. In our case, this method should add the loggingEvent to the list of loggingEvents. The code for setting up the logging becomes as follows:

    public void setupLogging(){
        AppenderBase<ILoggingEvent> appender = new AppenderBase<ILoggingEvent>() {
            @Override
            protected void append(ILoggingEvent iLoggingEvent) {
                loggingEvents.add(iLoggingEvent);
            }
        };
        appender.start();

        ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(objectUnderTest.getClass());
        logger.addAppender(appender);
        logger.setLevel(Level.ALL);
    }

Find logging statements

Since we’re using this for unittests, we only expect one statement with a specific message. To get the loggingEvent, if it’s there, we check all logging events:

    public Optional<ILoggingEvent> getLoggingEventWithMessage(String message){
        return loggingEvents.stream()
                .filter(iLoggingEvent -> iLoggingEvent.getMessage().equals(message))
                .findAny();
    }

How to use

Immagine you need to test the following snippet:

public class MessageLogger {
	public void logMessage(boolean bool, String message){
		if(bool){
			LOG.info("Message is {}", message);
		}
	}
}

The test we can write is the following:

public class MessageLoggerTest {
	
	@Test
	public void logMessage(){
		MessageLogger messageLogger = new MessageLogger();
		TestUtils testUtils = new TestUtils(messageLogger);
		testUtils.setupLogging();
		
		messageLogger.logMessage(true, "Hello World!");
		
		Optional<ILoggingEvent> loggingEvent = testUtils.getLoggingEventWithMessage("Message is {}");
		assertTrue(loggingEvent.isPresent());
	}
}

Note that we query for the unformatted message. The arguments can be retrieved using ILoggingEvent.getArgumentArray().

Conclusion

Sometimes we need to verify whether a logging statements has been logged in order to prove that the code under test does what it’s supposed to do. This utility class makes it easy to do so.
We’ve seen how to add a custom appender to a logback logger that stores the received loggingEvents in a list. We have created a method to query the list for an event with a specific message.
Next we have seen an example of how to use this functionality.

Send an email with Spring Boot and Thymeleaf

Using Spring Boot and Thymeleaf, it’s easy to send HTML emails to your users. This post will show the configuration and logic to send an email. We won’t go into details on how to setup the template, that’s for another time.

Project setup

Assuming you already use Spring Boot, we need to add two dependencies for this to work. The first is spring-boot-starter-mail, for sending the email. The second is spring-boot-starter-thymeleaf for handling the HTML template that we are going to use.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mail</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

Email configuration

We need to configure two beans. To send the email, we’re going to use Spring’s JavaMailSender. To resolve the template. we’re going to use Thymeleaf’s TemplateResolver. Since both beans are specifically configured for sending emails, we configure them in the same class.

JavaMailSender configuration

We need to create a properties file containing the mail server configuration. Let’s put it in src/main/resources/email/emailconfig.properties

email.host=mail.server.address
email.username=username
email.password=password

Next we’ll create the EmailConfiguration class.

@Configuration
@PropertySource("classpath:/email/emailconfig.properties")
public class EmailConfiguration {

    @Value("${email.host}")
    private String host;

    @Value("${email.username}")
    private String username;

    @Value("${email.password}")
    private String password;
}

Using @PropertySource we reference the properties file we created earlier. The @Value annotation is used to reference the property in the properties file.

Let’s configure the JavaMailSender bean next.

    @Bean
    public JavaMailSender getJavaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(host);
        mailSender.setPort(25);

        mailSender.setUsername(username);
        mailSender.setPassword(password);

        Properties props = mailSender.getJavaMailProperties();
        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.debug", "true");

        return mailSender;
    }

This sets up a JavaMailSender configured to use the smtp protocol. It’s mostly self explanatory.

TemplateEngine configuration

To resolve and process the template, we need to configure a separate TemplateEngine. This TemplateEngine will only use one TemplateResolver. You could configure more if you need to.

    @Bean
    public TemplateEngine emailTemplateEngine(){
        final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(templateResolver());
        return templateEngine;
    }

    private ITemplateResolver templateResolver() {
        final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setOrder(Integer.valueOf(1));
        templateResolver.setResolvablePatterns(Collections.singleton("*"));
        templateResolver.setPrefix("/email/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setCacheable(false);
        return templateResolver;
    }
}

This TemplateResolver will look for templates in src/main/resources/email, with a name ending in .html. It will handle the file as an HTML file.

Email service

Now that the beans are configured, we’re going to create a service that actually sends the email.

@Log
@Service
public class EmailService {

    public static final String SENDER_ADDRESS = "from@email.com";
    public static final String SUBJECT = "Subject of the email";
    private final TemplateEngine templateEngine;

    private final JavaMailSender emailSender;

    @Autowired
    public EmailService(JavaMailSender emailSender, TemplateEngine emailTemplateEngine){
        this.templateEngine = emailTemplateEngine;
        this.emailSender = emailSender;
    }

    public void send(Context context, String template, String address, String senderAddress, String subject){
        try {
            final MimeMessage mimeMessage = this.emailSender.createMimeMessage();
            final MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, "UTF-8");
            messageHelper.setFrom(senderAddress);
            messageHelper.setTo(address);
            messageHelper.setSubject(subject);

            final String htmlContent = this.templateEngine.process(template, context);
            messageHelper.setText(htmlContent, true);

            emailSender.send(mimeMessage);
        } catch (MessagingException e){
            log.warning(e.getMessage());
        }
    }
}

One thing to note here is the Context, which contains the data Thymeleaf uses to fill out the template. The ‘template’ parameter contains the filename of the template to use, e.g. ’email.html’. This file needs to be placed where the TemplateResolver will look, in src/main/resources/email.

Spring Boot – Load users from database

This is the third article in a series on authentication with Spring Boot.
In the first article we authenticated using social networks, and allowed any user to access our application.
In the second article we used inMemoryAuthentication for users that used the login form. In essence, we hardcoded our users.
This article is about adding users to a database. We are not going to allow users to sign up, we’re just going to add the users manually.

Setup Postgres

For our user entity, we want to save the following fields:

  • username
  • password (optional)
  • role
  • email
  • name

You can add the clientIds for the social networks that you allow your users to connect with for extra security. But we won’t do that here.
But we do want to be the email to be unique. Every user must have his own email propperty.

CREATE TABLE public."user"
(
    user_name text NOT NULL,
    password text,
    role text NOT NULL,
    email text NOT NULL,
    name text,
	UNIQUE(email)
)

To test this our login later, we need to add a user. The password is BCrypt encoded for “password”.

insert into "user"
(user_name, password, role, email, name)
values
('user','$2a$10$bXetyuwpEai6LomSykjZAuQ5mxU8WqhMBXGuWYnxlveCySRlGxh2i', 'USER', 'test@example.com', 'Test User')

JPA Database access

Once we have the database in place, it would be nice to actually use it in our code. To do this, we need to configure Spring to connect to our database, create a representation of the database in our code, and create a Repository that glues it together.
First, the configuration. Since we created the database schema ourselves, we don’t want Hibernate to do that. You could set spring.jpa.hibernate.ddl-auto to ‘validate’ to make sure the schema matches your model. Also, we want to show the SQL it is executing, so it’s easier to see what’s wrong. You want to turn this off when you’re done, because it generates a lot of logging.

spring:
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
  datasource:
    url: jdbc:postgresql://localhost:5432/database
    username: databaseuser
    password: password

For the model we’re going to use Lombok, so we don’t have to deal with the boilerplate code of getters and setters. We are using the Java Persistance API to handle the database mapping.

@Getter
@Setter
@Entity
@Table(name="user", schema = "public")
public class User {

    @Id
    private String email;

    @Column(name="user_name")
    private String userName;
    private String name;
    private String password;
    private String role;
}

We use a Repository to get the User objects from the database. Spring will do a lot of magic for us, so we only need to specify the JPA query and a method in an interface to retrieve the data.

@Repository
public interface UserRepository extends CrudRepository&lt;User, String&gt; {

    @Query("SELECT u FROM User u WHERE u.userName = :username")
    User getUserByUsername(String username);

    @Query("SELECT u FROM User u WHERE u.email = :email")
    User getUserByEmail(String email);
}

UserDetails and UserDetailsService

At this point we need to have UserDetails, and a service to get them from the database. Since we’re using bot FormLogin and OAuth2, I’ve decided to implement both UserDetails and OAuth2User in the same class. This makes things easier later on.

@Repository
public class MyUserDetails implements UserDetails, OAuth2User {

    private final User user;

    public MyUserDetails(User user){
        this.user = user;
    }

    @Override
    public Map&lt;String, Object&gt; getAttributes() {
        return Collections.emptyMap();
    }

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
        return Collections.singletonList(authority);
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String getName() {
        return user.getName();
    }
}

The service tries to load a user by username, and throws an exception when no user with that username could be found.

@Component
public class MyUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Autowired
    public MyUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userRepository.getUserByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("Could not find user");
        }

        return new MyUserDetails(user);
    }
}

Update FormLogin configuration

Previously we configured in-memory authentication. Now that we have all pieces in place to retrieve our users from the database, we need to configure it.
We need to configure the UserDetailsService.

@Bean
public UserDetailsService userDetailsService(){
	return new MyUserDetailsService(userRepository);
}

Then we’ll configure a DaoAuthenticationProvider using the UserDetailService. The passwordEncoder was already configured in the last blogpost.

@Bean
public DaoAuthenticationProvider authenticationProvider() {
	DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
	authProvider.setUserDetailsService(userDetailsService());
	authProvider.setPasswordEncoder(passwordEncoder());
	return authProvider;
}

And then to tie it toghether, we use this AuthenticationProvider as the source of the users for the LoginForm

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	auth.authenticationProvider(authenticationProvider());
}

Update OAuth configuration

Since we now use the database to store our users, we also need to update the OAuth configuration. We need to verify whether the user who’s trying to login using OAuth is actually known to us. The key information that we can use here is the email address. That’s why there is a method to get the user by email address in the repository, which we are going to use here. If there is no user with the email address that was found in the OAuth2 principle, we throw an exception. Otherwise, we return that user.

@Bean
public OAuth2UserService&lt;OAuth2UserRequest, OAuth2User&gt; oauth2UserService() {
	DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
	return request -> {
		OAuth2User auth2User = delegate.loadUser(request);
		String email = auth2User.getAttribute("email");
		User user = userRepository.getUserByEmail(email);
		if (user != null){
			return new MyUserDetails(user);
		}
		throw new InternalAuthenticationServiceException("User not registered");
	};
}

Update WebController and frontend

Now that we have all the pieces in place to use the database for user verification, we want to use this information on our site. Since there will be problems with some login attempts (maybe the user misspelled his username), we would like to be able to show an error message on the login page. So we update the /login endpoint like this:

@RequestMapping(value = "/login")
public String login(HttpServletRequest request, Model model){
	if (request.getSession().getAttribute("error.message")!= null) {
		String errorMessage = request.getSession().getAttribute("error.message").toString();
		log.info("Error message: "+errorMessage);
		model.addAttribute("errormessage", errorMessage);
	}
	return "login";
}

On the login page, we need to add the following to display this error message:

<div class="alert alert-danger" role="alert" th:if="${errormessage}">
    <span id="user" th:text="${errormessage}"></span>
</div>

In other places we would like to get the user’s name. To do this, we need to get the principal from the authentication token.

private Optional<MyUserDetails> extractMyUserDetails(Principal principal){
	if (principal instanceof UsernamePasswordAuthenticationToken) {
		return Optional.of((MyUserDetails) ((UsernamePasswordAuthenticationToken) principal).getPrincipal());
	} else if (principal instanceof OAuth2AuthenticationToken){
		return Optional.of((MyUserDetails) ((OAuth2AuthenticationToken) principal).getPrincipal());
	}
	log.severe("Unknown Authentication token type!");
	return Optional.empty();
}

And then we get the username from the MyUserDetails class

@RequestMapping(value = "/welcome")
public String welcome(Principal principal, Model model) {
	MyUserDetails userDetails = extractMyUserDetails(principal)
			.orElseThrow(IllegalStateException::new);
	model.addAttribute("name", userDetails.getName());
	return "welcome";
}

Spring Boot login with a form

Previously I wrote about securing your application with social login. But not everybody has a social account. In this article we’re going to add formlogin to the application. Formlogin simply means that your users can log in with a username and password. We’re going to keep it as simple as possible, with in-memory authentication.

Adding users

Since we’re going to use usernames and passwords to allow users to login, we need to define a password encoder. Spring will not compare the literal password that it receives, but encodes it and then compare the encoded passwords. When the passwords are stored in the database, you don’t want to see them as plain text.

@Bean
public BCryptPasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

The next step is to add the users. We’re going to use inMemoryAuthentication with a single user, to keep it as simple as possible.

public class WebsiteApplication extends WebSecurityConfigurerAdapter {

	// some more code

	protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
				.withUser("user").password(passwordEncoder().encode("password")).roles("USER");
	}
}

Note that we’re using the passwordEncoder defined in the previous step to encode the password here. Also, we need to define a role, even though we’re not using it yet.

Allow formlogin

Now we need to tell Spring Security to allow the use of our login form.

public class WebsiteApplication extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler("/");
		// @formatter:off
		http
			.authorizeRequests(
					a -&gt; a
				.antMatchers("/error", "/webjars/**","/oauth/**","/login").permitAll()
				.anyRequest().authenticated()
			)
			.formLogin(f -&gt; f
					.loginPage("/login").permitAll()
			)
			// some more code
			;
		// @formatter:on
	}
	// some more code
}

Add login form

Now that we have enabled the formlogin in the backend, it’s time to add a login form in the frontend.

<form name='loginForm' th:action="@{/login}" method='POST'>
	<table>
		<tr>
			<td>User:</td>
			<td><input type='text' name='username' value=''></td>
		</tr>
		<tr>
			<td>Password:</td>
			<td><input type='password' name='password' /></td>
		</tr>
        <tr>
            <td><input name="submit" type="submit" value="submit" /></td>
        </tr>
    </table>
</form>

Webcontroller changes

There were two methods that use the authentication information. The first, index(), doesn’t need to change at all. Sure, we get a different Authentication implementation, but it still works unaltered.

@RequestMapping(value = {"/","/index"})
public String index(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
	if (authentication.isAuthenticated()) {
		return "redirect:/welcome";
	}
	return "redirect:/login";
}

The second method, where we try to find the username of the authenticated user does change. Here we drop the @CurrentSecurityContext annotation, and request the Principal directly. However, based on the method of authentication, we need to do something else to get the username. If the user used the login form, we can get the username directly from the principal. When the user authenticated using OAuth2, we need to dig a little deeper.

@RequestMapping(value = "/welcome")
public String welcome(Principal principal, Model model) {
	if (principal instanceof UsernamePasswordAuthenticationToken) {
		model.addAttribute("name", principal.getName());
	} else if (principal instanceof OAuth2AuthenticationToken){
		model.addAttribute("name", ((OAuth2AuthenticationToken) principal).getPrincipal().getAttribute("name"));
	}
	return "welcome";
}

Conclusion

Adding a login form to a Spring Boot application is easy. We’ve hardcoded a user with in memory authentication. Since we’re going to use a password, we need to have a password encoder. We’ve configured Spring Security to allow the user to login using a form. Then we’ve actually added a basic HTML login form, and as a last step we’ve modified the backend to work with a different type of principal.

Spring Boot and Oauth2 with Thymeleaf

Spring has a good tutorial explaining how to authenticate with your application using one or more external authentication providers, like GitHub or Google. This tutorial uses a single page application with a Rest endpoint. For a personal project I didn’t want a single page application, I wanted to use Thymeleaf. During implementation I discovered a few things that I’d like to share. This post continues where the tutorial stopped, so you might want to read the tutorial first.

Configure custom OAuth2UserService

When the user has authenticated using an external service, you probably want to do something with that information. Most commonly you’d want to find the user in your own database. You need to have a hook where you can get access to the authenticated user details. To do this, you can create your own OAuth2UserService bean which will be executed when the user has been authenticated. The bean itself can be quite basic, the following example only returns the authenticated user:

 	@Bean
	public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
		DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
		return request -> {
			OAuth2User user = delegate.loadUser(request);

            // custom code

			return user;
		};
	}

Now we need to tell our application when to call this bean. In the configure(HttpSecurity http) method, we’re going to add the following fragment:

			.oauth2Login(o -> o.failureHandler((request, response, exception) -> {
						request.getSession().setAttribute("error.message", exception.getMessage());
						handler.onAuthenticationFailure(request, response, exception);
					})
				.userInfoEndpoint()
				.userService(oauth2UserService())
			);

Now we’ve specifically told Spring Security to use our own OAuth2UserService.

Authenticating with Google

Using our own custom OAuth2UserService, I discovered that authenticating with Google didn’t work. Or rather, the custom OAuth2UserService wasn’t executed while it was executed when using GitHub or Facebook. It turned out that when you don’t specify which scope you’re interested in, Google returned all scopes. Included in the list was “openid”, which is specifically filtered out by Spring Security. So, if you want to use your own OAuth2UserService with Google, you need to configure it with the scopes you need. Like this:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: google-client-ID
            clientSecret: google-client-secret
            scope:
              - email
              - profile

@CurrentSecurityContext

To obtain the authenticated user principal, the tutorial uses the annotation @AuthenticationPrincipal, like this:

    @GetMapping("/user")
    public Map<String, Object> user(@AuthenticationPrincipal OAuth2User principal) {
        return Collections.singletonMap("name", principal.getAttribute("name"));
    }

Usually you’re only interested in the user. However, if you need more information, you can use @SecurityContext. This article provides more information.
Here are two examples of how to use @CurrentSecurityContext with Thymeleaf:

    @RequestMapping(value = {"/","/index"})
    public String index(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
        if (authentication.isAuthenticated()) {
            return "redirect:/welcome";
        }
        return "redirect:/login";
    }
    @RequestMapping(value = "/welcome")
    public String welcome(@CurrentSecurityContext(expression = "authentication.principal") OAuth2User user, Model model) {
            model.addAttribute("name", user.getAttribute("name"));
            return "welcome";
    }

Custom Access Denied Page

The Spring Boot tutorial throws an unauthorized exception when the user tries to access a resource that he’s not allowed to. I wanted the website to redirect to the login page, assuming the user wasn’t authenticated. Or, if he was, then he should be redirected to the welcome page. We can configure this in the configure(HttpSecurity http) method:

			.exceptionHandling(e -> e
					.accessDeniedPage("/")
			)

This redirection ends up in the index() method of the previous section. Let’s look at that method again:

    @RequestMapping(value = {"/","/index"})
    public String index(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
        if (authentication.isAuthenticated()) {
            return "redirect:/welcome";
        }
        return "redirect:/login";
    }

When the user is authenticated, the user is redirected to the welcome page. Otherwise, the user is redirected to the login page.

Conclusion

While the Spring tutorial is quite good, there’s a lot more to OAuth2 authentication than it covers. This article covers some subjects that were beyond the scope of Spring’s tutorial. We’ve seen how to add your own processing of the authenticated user. Next we discussed some quirks when authenticating with Google. Then we’ve seen an alternative and more flexible way to get access to the user details. And last we redirected the access denied page to either the login page or the welcome page, using Thymeleaf.